/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.async;

import bgu.sonar.util.ex.UncheckedInterruptedException;
import bgu.sonar.util.ui.mon.Monitor;
import com.google.common.util.concurrent.UncheckedExecutionException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 *
 * @author bennyl
 */
public class FutureResult<T> {

    private final List<Callback<T>> callbacks = new LinkedList<>();
    /**
     * will get invoked before the cancel take place..
     */
    private final LinkedList<CancelHandler> cancelHandlers = new LinkedList<>();
    private boolean done = false;
    private T v = null;
    private Exception ex = null;
    private AsyncTask<T> owner;

    public FutureResult(AsyncTask<T> owner) {
        this.owner = owner;
    }

    void success(T v) {
        Callback[] all;
        synchronized (callbacks) {
            this.done = true;
            this.v = v;
            all = callbacks.toArray(new Callback[callbacks.size()]);
            callbacks.clear();
        }

        for (Callback<T> c : all) {
            c.onSuccess(v);
        }
    }

    void failed(Exception ex) {
        Callback[] all;
        synchronized (callbacks) {
            this.done = true;
            this.ex = ex;
            all = callbacks.toArray(new Callback[callbacks.size()]);
            callbacks.clear();
        }

        for (Callback<T> c : all) {
            c.onFailed(ex, ex instanceof InterruptedException);
        }

        if (all.length == 0 && ex != null && !owner.isCanceled() && !(ex instanceof InterruptedException)) {
            System.out.println("FutureResult For: " + owner.getClass().getSimpleName() + "/" + owner.getClass().getSuperclass().getSimpleName()
                    + " Report an error!");
            ex.printStackTrace(); //dont let the exception go unnoticed... 
        }
    }

    public boolean isCanceled() {
        return owner.isCanceled();
    }

    public void removeCallback(Callback<T> callback) {
        synchronized (callbacks) {
            callbacks.remove(callback);
        }
    }

    public void removeAllCallbacks() {
        synchronized (callbacks) {
            callbacks.clear();
        }

    }

    public void addCallback(Callback<T> callback) {
        synchronized (callbacks) {
            if (!done) {
                callbacks.add(callback);
                return;
            }
        }

        if (ex == null) {
            callback.onSuccess(v);
        } else {
            callback.onFailed(ex, ex instanceof InterruptedException);
        }
    }

    public void addCallback(final Callback<T> callback, ExecutorService pool) {
        pool.execute(new Runnable() {
            @Override
            public void run() {
                addCallback(callback);
            }
        });
    }

    public void cancel() {
        for (CancelHandler ca : cancelHandlers) {
            ca.handleCancel(false);
        }
        owner.cancel();
    }

    public void cancelAndWaitTermination() {
        try {
            for (CancelHandler ca : cancelHandlers) {
                ca.handleCancel(true);
            }
            owner.cancel();
            owner.awaitTerminationNoResult();
        } catch (InterruptedException ex) {
            throw new UncheckedInterruptedException(ex);
        }
    }

    public T awaitResult() {
        try {
            owner.awaitTermination();
            return getResult();
        } catch (InterruptedException ex) {
            throw new UncheckedInterruptedException(ex);
        }
    }

    /**
     * @return can return null if the operation does not completed yet
     */
    public T getResult() {
        if (ex != null) {
            throw new UncheckedExecutionException(ex);
        }
        return v;
    }

    /**
     * same as getResult but if the task failed will return null.
     *
     * @return
     */
    public T getResultIgnoreFailure() {
        return v;
    }

    /**
     * note that if already done - the task will get exeucted in the current
     * thread - this is probably not what you want! TODO - consider remove.
     *
     * @param <V>
     * @param c
     * @return
     */
    public <V> FutureResult<V> chain(ChainedAsyncTask<T, V> c) {
        c.setChainFor(this);
        addCallback(c);
        return c.getResult();
    }

    /**
     * when done execute the next on the given executor service
     *
     * @param <V>
     * @param c
     * @param pool
     * @return
     */
    public <V> FutureResult<V> chain(final ChainedAsyncTask<T, V> c, final ExecutorService pool) {
        c.setChainFor(this);
        addCallback(new ChainedAsyncTask<T, Void>() {
            @Override
            public Void call() throws Exception {
                pool.execute(c);
                return null;
            }
        });

        return c.getResult();
    }

    public void addCancelHandler(CancelHandler cancler) {
        cancelHandlers.add(cancler);
    }

    public static <T> FutureResult<T> immidiate(final T r) {
        return new AsyncTask<T>() {
            @Override
            public T call() throws Exception {
                return r;
            }
        }.executeInSameThread();
    }

    /**
     * can return null if the task that this result belongs to does not extends MonitoredTask
     *
     * @return
     */
    public Monitor getTaskMonitor() {
        if (owner instanceof MonitoredAsyncTask) {
            return ((MonitoredAsyncTask) owner).getMonitor();
        }
        return null;
    }
}
